function model = svmtrain(labels, tdata, opts)
% svmtrain  - train libSVM based classifier
%
% FORMAT:       model = svmtrain(labels, tdata, opts)
%
% Input fields:
% 
%       labels      Lx1 double array with labels
%       tdata       LxT double training data (with T features/dims)
%       opts        libSVM options
%        .cachesize cache memory size in MByte (default: 100)
%        .coef0     coef0 in kernel function (default: 0)
%        .degree    degree in kernel function (default: 3)
%        .gamma     gamma in kernel function (default: 1/k)
%        .kerntype  either of {'linear'}, 'poly', 'pre', 'rad', 'sig'
%                   or numeric value as with libSVM (see output of
%                   svmtrainc without arguments)
%        .ncross    n-fold cross validation (default: 0)
%        .probest   flag, prob. estimates for SVC/SVR (default: false)
%        .quiet     flag, quiet output mode (default: true)
%        .shrinkh   flag, use shrinking heuristics (default: true)
%        .svmcost   cost param in C/eps/nuSVR classifier (default: 1)
%        .svmeps    epsilon param in eps classifier (default: 0.1)
%        .svmnu     nu param in nuSVC/nuSVR/oneclass classifier
%        .svmtype   either of {'C'}, 'eps', 'nuSVC', 'nuSVR', 'oneclass'
%                   or numeric value as with libSVM (see output of
%                   svmtrainc without arguments)
%        .toleps    tolerance eps termination criterion (default: 0.001)
%        .weights   give weights for each class label (default: 1xC ones)
%
% Output fields:
%
%       model       1x1 struct with fields
%        .Parameters (input parameters)
%        .nr_class  number of classes
%        .totalSV
%        .rho
%        .Label     labels of classes
%        .ProbA     a probability
%        .ProbB     b probability
%        .nSV       number of samples per class
%        .sv_coef   SV coefficients
%        .SVs       SVs
%
% Note: this functions uses the MEX-file svmtrainc, which is courtesy
%       of Chih-Jen Lin, being part of the the libSVM library,
%       see http://www.csie.ntu.edu.tw/~cjlin/libsvm/ for details

% Version:  v0.7g
% Build:    9072019
% Date:     Jul-20 2009, 7:58 PM CEST
% Author:   Chih-Jen-Lin, Department of CS, National Taiwan University
% Editor:   Jochen Weber, SCAN Unit, Columbia University, NYC, NY, USA
% URL/Info: http://www.csie.ntu.edu.tw/~cjlin/libsvm/

% rudimentary argument check
if nargin < 2 || ...
   ~isa(labels, 'double') || ...
    isempty(labels) || ...
    numel(labels) ~= size(labels, 1) || ...
    any(isinf(labels) | isnan(labels)) || ...
   ~isa(tdata, 'double') || ...
    isempty(tdata) || ...
    size(tdata, 1) ~= numel(labels) || ...
    ndims(tdata) > 2 || ...
    any(isinf(tdata(:)) | isnan(tdata(:)))
    error( ...
        'BVQXtools:BadArgument', ...
        'Invalid argument in call to svmtrain.' ...
    );
end
ncl = numel(unique(labels(:)));
if nargin < 3 || ...
   ~isstruct(opts) || ...
    numel(opts) ~= 1
    opts = struct;
end

% parse options
if ~isfield(opts, 'cachesize') || ...
   ~isa(opts.cachesize, 'double') || ...
    numel(opts.cachesize) ~= 1 || ...
    isinf(opts.cachesize) || ...
    isnan(opts.cachesize) || ...
    opts.cachesize < 16 || ...
    opts.cachesize > 512
    opts.cachesize = 100;
else
    opts.cachesize = floor(opts.cachesize);
end
if ~isfield(opts, 'coef0') || ...
   ~isa(opts.coef0, 'double') || ...
    numel(opts.coef0) ~= 1 || ...
    isinf(opts.coef0) || ...
    isnan(opts.coef0)
    opts.coef0 = 0;
end
if ~isfield(opts, 'gamma') || ...
   ~isa(opts.gamma, 'double') || ...
    numel(opts.gamma) ~= 1 || ...
    isinf(opts.gamma) || ...
    isnan(opts.gamma)
    opts.gamma = '';
end
if ~isempty(opts.gamma)
    opts.gamma = sprintf(' -g %f', opts.gamma);
end
if ~isfield(opts, 'degree') || ...
   ~isa(opts.degree, 'double') || ...
    numel(opts.degree) ~= 1 || ...
    isinf(opts.degree) || ...
    isnan(opts.degree) || ...
    opts.degree < 1 || ...
    opts.degree > 9
    opts.degree = 3;
else
    opts.degree = floor(opts.degree);
end
if ~isfield(opts, 'kerntype') || ...
   (~ischar(opts.kerntype) && ...
   (~isa(opts.kerntype, 'double') || ...
     numel(opts.kerntype) ~= 1 || ...
     isinf(opts.kerntype) || ...
     isnan(opts.kerntype) || ...
    ~any((0:4) == opts.kerntype)))
    opts.kerntype = 0;
elseif ischar(opts.kerntype)
    switch (lower(opts.kerntype(:)'))
        case {'1', 'poly', 'polynomial'}
            opts.kerntype = 1;
        case {'2', 'rad', 'radial'}
            opts.kerntype = 2;
        case {'3', 'sig', 'sigmoid'}
            opts.kerntype = 3;
        case {'4', 'pre', 'precomputed'}
            opts.kerntype = 4;
        otherwise
            opts.kerntype = 0;
    end
end
if ~isfield(opts, 'ncross') || ...
   ~isa(opts.ncross, 'double') || ...
    numel(opts.ncross) ~= 1 || ...
    isinf(opts.ncross) || ...
    isnan(opts.ncross) || ...
    opts.ncross < 0 || ...
    opts.ncross > 1000
    opts.ncross = 0;
else
    opts.ncross = floor(opts.ncross);
end
if opts.ncross >= 2
    opts.ncross = sprintf(' -v %d', opts.ncross);
else
    opts.ncross = '';
end
if ~isfield(opts, 'probest') || ...
   ~islogical(opts.probest) || ...
    numel(opts.probest) ~= 1 || ...
   ~opts.probest
    opts.probest = 0;
else
    opts.probest = 1;
end
if ~isfield(opts, 'quiet') || ...
   ~islogical(opts.quiet) || ...
    numel(opts.quiet) ~= 1
    opts.quiet = true;
end
if opts.quiet
    opts.quiet = ' -q';
else
    opts.quiet = '';
end
if ~isfield(opts, 'shrinkh') || ...
   ~islogical(opts.shrinkh) || ...
    numel(opts.shrinkh) ~= 1 || ...
    opts.shrinkh
    opts.shrinkh = 1;
else
    opts.shrinkh = 0;
end
if ~isfield(opts, 'svmcost') || ...
   ~isa(opts.svmcost, 'double') || ...
    numel(opts.svmcost) ~= 1 || ...
    isinf(opts.svmcost) || ...
    isnan(opts.svmcost) || ...
    opts.svmcost <= 0 || ...
    opts.svmcost >= 1e7
    opts.svmcost = 1;
end
if ~isfield(opts, 'svmnu') || ...
   ~isa(opts.svmnu, 'double') || ...
    numel(opts.svmnu) ~= 1 || ...
    isinf(opts.svmnu) || ...
    isnan(opts.svmnu) || ...
    opts.svmnu < -1e7 || ...
    opts.svmnu > 1e7
    opts.svmnu = 0.5;
end
if ~isfield(opts, 'svmeps') || ...
   ~isa(opts.svmeps, 'double') || ...
    numel(opts.svmeps) ~= 1 || ...
    isinf(opts.svmeps) || ...
    isnan(opts.svmeps) || ...
    opts.svmeps <= 0 || ...
    opts.svmeps >= 1e7
    opts.svmeps = 0.1;
end
if ~isfield(opts, 'svmtype') || ...
   (~ischar(opts.svmtype) && ...
   (~isa(opts.svmtype, 'double') || ...
     numel(opts.svmtype) ~= 1 || ...
     isinf(opts.svmtype) || ...
     isnan(opts.svmtype) || ...
    ~any((0:4) == opts.svmtype)))
    opts.svmtype = 0;
elseif ischar(opts.svmtype)
    switch (lower(opts.svmtype(:)'))
        case {'1', 'nusvc', 'nu-svc'}
            opts.svmtype = 1;
        case {'2', 'one', 'oneclass', 'one-class'}
            opts.svmtype = 2;
        case {'3', 'e', 'eps', 'epsilon', 'epsilon-svr'}
            opts.svmtype = 3;
        case {'4', 'nusvr', 'nu-svr'}
            opts.svmtype = 4;
        otherwise
            opts.svmtype = 0;
    end
end
if ~isfield(opts, 'toleps') || ...
   ~isa(opts.toleps, 'double') || ...
    numel(opts.toleps) ~= 1 || ...
    isinf(opts.toleps) || ...
    isnan(opts.toleps) || ...
    opts.toleps <= 0 || ...
    opts.toleps > 0.5
    opts.toleps = 0.001;
end
if ~isfield(opts, 'weights') || ...
   ~isdouble(opts.weights) || ...
    numel(opts.weights) ~= ncl || ...
    any(isinf(opts.weights(:)) | isnan(opts.weights(:)) | opts.weights(:) <= 0)
    opts.weights = '';
end
if ~isempty(opts.weights)
    w = '';
    for wc = 1:numel(opts.weights)
        w = sprintf('%s -w%d %f', w, wc, opts.weights(wc));
    end
    opts.weights = w;
end
optsstring = sprintf( ...
    '-s %d -t %d -d %d%s -r %f -c %f -p %f -m %d -e %f -h %d -b %d%s%s%s', ...
    opts.svmtype, opts.kerntype, opts.degree, opts.gamma, opts.coef0, ...
    opts.svmcost, opts.svmeps, ...
    opts.cachesize, opts.toleps, opts.shrinkh, opts.probest, ...
    opts.weights, opts.ncross, opts.quiet);

% pass on
model = svmtrainc(labels, tdata, optsstring);
